昨天我們初步認識了 RAG 的基本概念,而在實際應用中,第一步就是將外部資料整理成 AI 能理解與處理的形式。無論是 PDF、Markdown 還是 HTML,這些文件都需要先被載入並分割成適合語言模型處理的片段,之後才能進一步轉換成向量並存入資料庫。
今天我們會深入介紹 LangChain 的 文件載入器(Document Loaders) 與 文本分割器(Text Splitters),並透過程式範例,示範如何把一份長篇文件整理成結構化資料,為後續的檢索與生成做好準備。
大型語言模型(LLM)在處理輸入時受限於 上下文長度(Context Length),這個限制決定了模型一次能接收的最大 Token 數量。然而,真實世界的文件往往是成千上萬字的長篇內容,例如數十頁的 PDF、技術規格文件,甚至是完整的書籍。
如果我們直接將整份文件丟進模型,不僅可能超過 Token 上限而被截斷,還會顯著增加推論成本與延遲。更重要的是,長文本通常包含大量與問題無關的資訊,反而會干擾模型的推理,導致回答精準度下降。
因此,在將文件交給模型之前,我們需要對文件進行兩個步驟的預處理:
Document
物件,並附上 metadata
(來源、頁碼、分類等),方便後續檢索與引用。透過這樣的處理流程,我們能在保留語意完整性的同時,將文件轉換成模型可高效處理的格式。這不僅能有效降低 Token 成本,也能讓模型在檢索與回答時維持更高的準確度與上下文一致性。
在 RAG 系統中,第一步就是將原始資料整理成模型可處理的格式。LangChain 提供了 Document Loaders,支援多種常見的檔案格式與資料來源,這些載入器由官方或社群維護,能大幅簡化資料導入流程。
它的核心功能是:將來源資料解析並轉換為統一結構的 Document[]
陣列,每個 Document
物件包含兩部分:
pageContent
:實際的文字內容。metadata
:額外資訊(例如檔案路徑、頁碼、URL、分類標籤等)。這樣的結構能在後續進行文本分割、向量化與語意檢索時,提供一致且統一的處理方式。
LangChain 提供文件載入器大致可分為兩大類:
以下整理一些常用的載入器與對應套件位置:
文件類型 | 文件載入器 | 對應套件路徑 |
---|---|---|
純文字檔 | TextLoader |
langchain/document_loaders/fs/text |
PDF 文件 | PDFLoader |
@langchain/community/document_loaders/fs/pdf |
HTML 網頁 | CheerioWebBaseLoader |
@langchain/community/document_loaders/web/cheerio |
多檔資料夾 | DirectoryLoader |
langchain/document_loaders/fs/directory |
Note:部分載入器需要額外安裝第三方套件(例如 PDF 載入器依賴
pdf-parse
、HTML 載入器依賴cheerio
)。在使用前,務必確認已安裝相關依賴。
適用於 .txt
、.md
等純文字文件。由於純文字檔沒有複雜結構,載入速度快且相對穩定。
import { TextLoader } from 'langchain/document_loaders/fs/text';
const loader = new TextLoader('data/intro.txt');
const docs = await loader.load();
console.log('文件內容:', docs[0].pageContent);
需要先安裝 pdf-parse
套件。可選擇一次載入整份 PDF,或按頁分割成多個 Document
。
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
const loader = new PDFLoader('data/annual_report.pdf');
const docs = await loader.load();
console.log('頁數:', docs.length);
console.log('第一頁內容:', docs[0].pageContent);
需要先安裝 cheerio
套件。會自動解析 HTML 並擷取純文字內容(例如 <p>
、<h1>
等)。
import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio';
const loader = new CheerioWebBaseLoader('https://example.com');
const docs = await loader.load();
console.log('網頁內容:', docs[0].pageContent);
可一次載入資料夾內多個文件,並依副檔名選擇對應的載入器,方便批次處理。
import { DirectoryLoader } from 'langchain/document_loaders/fs/directory';
import { TextLoader } from 'langchain/document_loaders/fs/text';
const loader = new DirectoryLoader('data', {
'.txt': (path) => new TextLoader(path),
'.md': (path) => new TextLoader(path),
});
const docs = await loader.load();
console.log(`共載入 ${docs.length} 個 Document`);
在 LangChain 中,無論資料來源是檔案、網頁或 API,最終都會被轉換成 Document[]
陣列。這確保後續的文本分割、向量化與語意檢索都能遵循一致的處理流程。
透過這種抽象化設計,開發者不必擔心資料來源的差異,就能專注於應用邏輯,並在需要時靈活地替換或擴充模組。
在 RAG 或其他需要處理長篇內容的應用中,文件分割(Document Splitting) 幾乎是不可或缺的前處理步驟。它的核心概念是:將長篇文件切割成較小、易於處理的片段(chunks),同時盡量保留必要的上下文資訊。
在實務上,文件分割有以下幾個主要目的:
在 LangChain 中,Text Splitters 就是專門負責這項任務的工具。它會將 Document[]
中的文字內容切分成多個小片段,並保留原始的 metadata
,以便在檢索階段能回溯至原始來源。
如何有效地切割長篇內容,是打造高品質 RAG 系統的關鍵之一。根據應用情境與資料型態,可以採用不同的策略,每種方法各有優勢與適用場景。
最直觀的方式是依據文字長度進行切割,確保每個片段都不會超過指定大小。這種方法雖然簡單,但在多數情境下都非常實用。其優點包括:
根據計算單位不同,基於長度的切割可分為:
以下為使用 LangChain CharacterTextSplitter
進行字元切割的範例:
import { CharacterTextSplitter } from "@langchain/textsplitters";
const textSplitter = new CharacterTextSplitter({
chunkSize: 100,
chunkOverlap: 0,
});
const texts = await textSplitter.splitText(document);
這段程式會將輸入的 document
依字元數切分為多個小片段,確保每段長度不超過上限。
chunkSize
:每個片段的最大長度(此例為 100 個字元)。chunkOverlap
:片段間的重疊字元數(此例為 0,表示不重疊)。Tip:在 RAG 應用中,通常會設定
chunkOverlap
(例如 50–100 tokens),以保留跨段落的上下文,避免語意斷裂。
自然語言文本本身具備層級結構,例如 段落 → 句子 → 單詞。在切割文件時,沿用這些語法與語意層級,可以保留語句的流暢性與語意完整性,避免像單純依長度切割時,可能出現斷句不自然或語意中斷的問題。
LangChain 提供的 RecursiveCharacterTextSplitter
正是這種策略的代表性實作。它的核心運作邏輯如下:
chunkSize
的片段。使用範例如下:
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 100,
chunkOverlap: 0,
});
const texts = await textSplitter.splitText(document);
這種遞迴式的切割方式,能讓片段在語意上盡可能保持完整,同時確保每段內容符合模型的輸入長度要求,是處理長篇自然語言文本時的常用選擇。
有些文件天生就具備明確的層次結構,例如 HTML 網頁、Markdown 文件、JSON 資料或程式碼檔案。這些格式通常已經依章節、段落、欄位或功能區塊進行自然分組,因此我們可以直接利用這些結構來切割文件,而不必完全依賴長度或語意分析。
基於文件結構的切割主要有以下優點:
常見應用範例:
需要注意的是,文件結構可能存在多層嵌套或混合格式,例如 HTML 內嵌 Markdown。這時候切割邏輯必須能同時處理不同層級的解析,才能正確保留內容的階層關係。此外,若單一結構單位本身過於龐大,仍建議搭配 基於長度 的切割策略進一步細分,以避免超出模型輸入限制,並確保後續處理的效率與穩定性。
與前面依長度或結構切割的方式不同,基於語意的分割並不是單純依照字數或標點符號來決定斷點,而是直接分析文本的語意內容,找出自然的轉折點。它的核心理念是:當主題或語意出現明顯變化時,才進行切割,以確保每個片段在語意上完整且連貫。
這種方法的典型實作流程通常依賴 Embedding 相似度分析:
這種方法的最大優勢是能夠保證每個片段在語意上的一致性與完整性,對於 語意檢索、文件摘要、問答系統 等應用的精準度有顯著提升。不過,它的缺點也很明顯:需要將大量文字反覆轉換成 Embedding,並進行多次相似度比較,因此計算成本高,實作邏輯也比單純依長度或結構切割更複雜。
Note:所謂 Embedding,就是把一段文字轉換成一個多維向量,用數字的方式表示該文字的語意特徵。這樣不同的文字就能透過向量之間的距離或相似度來比較語意上的接近程度。後續內容我們會更深入介紹 Embedding 的概念與用法。
今天我們學會了如何用 LangChain 的 文件載入器(Document Loaders) 與 文本分割器(Text Splitters),把外部資料整理成 AI 能有效處理的格式:
Document[]
,並附帶 metadata
以便後續追蹤。TextLoader
、PDFLoader
、CheerioWebBaseLoader
、DirectoryLoader
,部分需要額外安裝第三方套件。文件載入與分割雖然只是 RAG 流程的第一步,但它決定了後續檢索與生成的品質,是打造可靠 AI 應用的基礎。